/*
 * Gedigarr - Falling block game.
 * Copyright (C) 2010  BoredCorp
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
package gedigarr.game;

import gedigarr.timers.GameTimer;
import gedigarr.grid.GameGrid;
import gedigarr.grid.MenuGrid;
import gedigarr.grid.ScoreGrid;
import gedigarr.grid.ScoreInputGrid;
import gedigarr.input.InputHandler;
import java.awt.event.KeyEvent;

/**
 * Handles states, timers, and manages user input. Passes grid manipulation 
 * commands to GameGrid. The state may, at any time, be any of the following:
 * Running; Paused; Menu; Scores; Gameover; Exiting.
 * <p>
 * r10<br>
 * Moved Timers to GameTimer
 * r9<br>
 * Added this javadoc.
 * r8<br>
 * Refactored class. Moved Grid and Tetromino methods to GameGrid class.
 * @author JC
 */
public class GameState {

  public static final int STATE_RUNNING = 0x00;
  public static final int STATE_PAUSED = 0x01;
  public static final int STATE_MENU = 0x02;
  public static final int STATE_SCORES = 0x03;
  public static final int STATE_SCORE_INPUT = 0x04;
  public static final int STATE_GAMEOVER = 0x05;
  public static final int STATE_EXITING = 0x06;
  private boolean hMoved;
  private boolean vMoved;
  private int state;
  private int newState;
  private final GameScore score = new GameScore();
  private final GameGrid grid = new GameGrid();
  private final MenuGrid menuGrid = new MenuGrid();
  private final ScoreGrid scoreGrid = new ScoreGrid( score );
  private final ScoreInputGrid inputGrid = new ScoreInputGrid( score );
  private GameTimer tVert = new GameTimer( 0L, GameConstants.MOVE_BASE_VER );
  private GameTimer tRotate = new GameTimer( 0L, GameConstants.MOVE_BASE_ROTATE );
  private GameTimer tRight = new GameTimer( 0L, GameConstants.MOVE_BASE_HOR );
  private GameTimer tLeft = new GameTimer( 0L, GameConstants.MOVE_BASE_HOR );
  private GameTimer time = new GameTimer( 0L );
  private int level = 0;

  public GameState() {
    //default state running
    this( STATE_RUNNING );
  }

  public GameState( int defaultState ) {
    requestStateChange( defaultState );
    reset();
    menuGrid.setup();
  }

  public void update( InputHandler input, long delta ) {
    initUpdate();
    input.pollPressed();
    inputMod( input, delta );
    applyGameRules( delta );
    finaliseUpdate();
  }

  private void initUpdate() {
    grid.setActive( false, false );
    this.hMoved = false;
    this.vMoved = false;
    setState( newState );
  }

  private void finaliseUpdate() {
    grid.refreshGhost();
    grid.setActive( false, true );
  }

  private void reset() {
    grid.setup();
    score.resetScore();
    time.reset();
    level = 0;
    tVert.setTick( calcDropSpeed() );
  }

  /**
   * Requests a new state. The state will be changed at the start of the next 
   * update cycle.
   * @param newState The requested state.
   */
  public void requestStateChange( int newState ) {
    this.newState = newState;
  }

  /**
   * Forces a new state. The state will be set immediately. Should only be used 
   * when the update cycle is not running.
   * @param state This state will be set on this methods return.
   */
  private void setState( int state ) {
    if ( this.newState != this.state ) {
      this.state = state;
      stateChanged();
    }
  }

  private void stateChanged() {
    switch ( this.state ) {
      case STATE_GAMEOVER:
        //score.addHiScore( "TEST", getScore(), getLevel(), getTimeMillis() / 1000 );
        //scoreGrid.refreshGrid();
        boolean hiScore = inputGrid.newScore( this.getScore() );
        if ( hiScore ) {
          requestStateChange( STATE_SCORE_INPUT );
        }
        break;
      case STATE_SCORES:
        scoreGrid.refreshGrid();
        break;
      case STATE_SCORE_INPUT:
        inputGrid.setState( ScoreInputGrid.STATE_ENTRY );
        break;
    }
  }

  private void applyGameRules( long delta ) {
    //decend on vertical tick
    if ( this.state == STATE_RUNNING ) {
      time.inc( delta );

      tVert.inc( delta );
      if ( !vMoved && tVert.hasTicked() ) {
        tVert.tick();
        moveDown();
      }
    } else if ( this.state == STATE_SCORE_INPUT ) {
      switch ( inputGrid.getState() ) {
        case ScoreInputGrid.STATE_ENTRY:
          inputGrid.applyMove();
          break;
        case ScoreInputGrid.STATE_CONFIRMED:
          score.addHiScore( inputGrid.getName(), score.getScore(),
                  this.getLevel(), this.getTimeMillis() / 1000 );
          inputGrid.setState( ScoreInputGrid.STATE_INACTIVE );
          requestStateChange( STATE_MENU );
      }
    }

    int clear = grid.dropAll();
    if ( clear > 0 ) {
      double clearRat = (10.0 + clear) / 10.0;
      double lvlRat = (10.0 + level) / 10.0;
      score.addToScore( (long) (clear * GameConstants.SCORE_LINE * clearRat * lvlRat) );
    }

    if ( (grid.getClearedRowCount() / GameConstants.LEVEL_UP) > this.level ) {
      incLevel();
    }

    //*** RECALCULATE THE DROP SPEED
    calcDropSpeed();
  }

  private void incLevel() {
    this.level++;
    tVert.setTick( calcDropSpeed() );
  }

  private int calcDropSpeed() {
    int dropSpeed = (int) GameConstants.MOVE_BASE_VER - ((this.getLevel() * GameConstants.TIME_INC_LEVEL)
            + ((int) (getTimeMillis() / 1000) * GameConstants.TIME_INC_SECOND));
    return Math.max( dropSpeed, GameConstants.MIN_DROP_SPEED );
  }

  private void inputMod( InputHandler input, long delta ) {
    hMoved = false;
    int keyState = 0;

    //onPause( keyState, delta );

    //if ( !stateChanged ) {
    keyState = input.getKeyState( KeyEvent.VK_UP );
    onUp( keyState, delta );

    keyState = input.getKeyState( KeyEvent.VK_DOWN );
    onDown( keyState, delta );

    keyState = input.getKeyState( KeyEvent.VK_LEFT );
    onLeft( keyState, delta );

    keyState = input.getKeyState( KeyEvent.VK_RIGHT );
    onRight( keyState, delta );

    keyState = input.getKeyState( KeyEvent.VK_P );
    onP( keyState, delta );

    keyState = input.getKeyState( KeyEvent.VK_SPACE );
    onSpace( keyState, delta );

  }

  private void onLeft( int keyState, long delta ) {
    switch ( state ) {
      case STATE_RUNNING:
        switch ( keyState ) {
          case InputHandler.STATE_PRESSED:
            if ( !hMoved && grid.moveActiveLeft() ) {
              hMoved = true;
            }
            tLeft.inc( delta );
            break;
          case InputHandler.STATE_HELD:
            tRight.reset(); // stops double held flicking
            tLeft.inc( delta );
            if ( !hMoved && tLeft.hasTicked() ) {
              if ( !hMoved && grid.moveActiveLeft() ) {
                hMoved = true;
                tLeft.tick();
              }

            }
            break;
          case InputHandler.STATE_RELEASED:
            tLeft.reset();
            break;
        }
        break;
      case STATE_MENU:
        switch ( keyState ) {
          case InputHandler.STATE_PRESSED:
            menuGrid.moveUp();
            //menuGrid.addMove( MenuGrid.MOVE_LEFT );
            break;
        }
        break;
      case STATE_SCORE_INPUT:
        if ( keyState == InputHandler.STATE_PRESSED ) {
          System.out.println( "LEFT" );
          inputGrid.moveCursor( ScoreInputGrid.MOVE_LEFT );
        }
        break;
    }
  }

  private void onRight( int keyState, long delta ) {
    switch ( this.state ) {
      case STATE_RUNNING:
        switch ( keyState ) {
          case InputHandler.STATE_PRESSED:
            if ( !hMoved && grid.moveActiveRight() ) {
              hMoved = true;
            }
            tRight.inc( delta );
            break;
          case InputHandler.STATE_HELD:
            tLeft.reset(); // stops double held flicking
            tRight.inc( delta );
            if ( tRight.hasTicked() ) {
              if ( !hMoved && grid.moveActiveRight() ) {
                hMoved = true;
                tRight.tick();
              }

            }
            break;
          case InputHandler.STATE_RELEASED:
            tRight.reset();
            break;
        }
        break;
      case STATE_MENU:
        switch ( keyState ) {
          case InputHandler.STATE_PRESSED:
            menuGrid.moveDown();
            //menuGrid.addMove( MenuGrid.MOVE_RIGHT );
            break;
        }
        break;
      case STATE_SCORE_INPUT:
        if ( keyState == InputHandler.STATE_PRESSED ) {
          inputGrid.moveCursor( ScoreInputGrid.MOVE_RIGHT );
        }
        break;
    }
  }

  private void onDown( int keyState, long delta ) {
    switch ( this.state ) {
      case STATE_RUNNING:
        switch ( keyState ) {
          case InputHandler.STATE_PRESSED:
          case InputHandler.STATE_HELD:
            if ( !hMoved ) {
              moveDown();
            }

            break;
        }
        break;
      case STATE_MENU:
        switch ( keyState ) {
          case InputHandler.STATE_PRESSED:
            menuGrid.moveDown();
            //menuGrid.addMove( MenuGrid.MOVE_DOWN );
            break;
        }
        break;
      case STATE_SCORE_INPUT:
        if ( keyState == InputHandler.STATE_PRESSED ) {
          inputGrid.moveCursor( ScoreInputGrid.MOVE_DOWN );
        }
        break;
    }
  }

  private void moveDown() {
    if ( grid.moveActiveDown() ) {
      vMoved = true;
      score.addToScore( GameConstants.SCORE_TICK * level );
    } else {
      score.addToScore( GameConstants.SCORE_PLACE * level );
      onTetroPlaced();
    }
  }

  private void onUp( int keyState, long delta ) {
    // Separate game states
    switch ( this.state ) {
      case STATE_RUNNING:
        // Separate key states
        switch ( keyState ) {
          case InputHandler.STATE_PRESSED:
            grid.rotate();
            break;
          case InputHandler.STATE_HELD:
            tRotate.inc( delta );
            if ( tRotate.hasTicked() ) {
              grid.rotate();
              tRotate.tick();
            }

            break;
          case InputHandler.STATE_RELEASED:
            tRotate.reset();
        } // end switch( keystate )

        break;
      case STATE_MENU:
        switch ( keyState ) {
          case InputHandler.STATE_PRESSED:
            menuGrid.moveUp();
            break;
          //menuGrid.addMove( MenuGrid.MOVE_UP );
        }
        break;
      case STATE_SCORE_INPUT:
        if ( keyState == InputHandler.STATE_PRESSED ) {
          inputGrid.moveCursor( ScoreInputGrid.MOVE_UP );
        }
        break;
    }

  }

  private void onP( int keyState, long delta ) {
    if ( keyState == InputHandler.STATE_PRESSED ) {
      switch ( this.state ) {
        case STATE_RUNNING:
          requestStateChange( STATE_PAUSED );
          break;
        case STATE_PAUSED:
          requestStateChange( STATE_RUNNING );
          break;
      }

    }
  }

  private void onSpace( int keyState, long delta ) {
    if ( keyState == InputHandler.STATE_PRESSED ) {
      switch ( this.state ) {
        case STATE_GAMEOVER:
        case STATE_SCORES:
          requestStateChange( STATE_MENU );
          break;
        case STATE_RUNNING:
          int dropCount = grid.dropActive();
          if ( dropCount > 0 ) {
            vMoved = true;
            score.addToScore( dropCount * GameConstants.SCORE_TICK * level );
            onTetroPlaced();
          }
          break;
        case STATE_MENU:
          switch ( menuGrid.getSelectedItem() ) {
            case MenuGrid.ACTION_NEW_GAME:
              reset();
              requestStateChange( STATE_RUNNING );
              break;
            case MenuGrid.ACTION_EXIT:
              requestStateChange( STATE_EXITING );
              break;
            case MenuGrid.ACTION_SCORES:
              requestStateChange( STATE_SCORES );
              break;
          }
          break;
        case STATE_SCORE_INPUT:
          inputGrid.selectActive();
          break;
      }

    }
  }

  private void onTetroPlaced() {
    grid.setActive( true, true );
    if ( grid.newBlock() ) {
      tVert.setTick( calcDropSpeed() );
    } else {
      requestStateChange( STATE_GAMEOVER );
    }
  }

  public GameGrid getGameGrid() {
    return grid;
  }

  public MenuGrid getMenuGrid() {
    return menuGrid;
  }

  public ScoreGrid getScoreGrid() {
    return scoreGrid;
  }

  public ScoreInputGrid getInputGrid() {
    return inputGrid;
  }

  public int getState() {
    return state;
  }

  public long getScore() {
    return score.getScore();
  }

  public long getTimeMillis() {
    return this.time.getCount();
  }

  public int getLevel() {
    return this.level;
  }

  public int getCleared() {
    return grid.getClearedRowCount();
  }

  public long getHighScore() {
    return score.getHighScore();
  }
}
